/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Core -- all the core functionality that makes Gimme work; the bare essentials if you will
*
*/

var Gimme = function()
{
	// keep track of event handlers so they can be properly detached in IE
	var evtHash = {};
	
	var gimme = function(_selector, _gimmeCallback)
	{
		this.entities = ComplexGimme(_selector, null, _gimmeCallback);
		this.length = this.entities.length;
	};
	
	// == Core Gimme == //	
	gimme.prototype.element = function(n)
	{
		return this.entities[n || 0];
	};
	
	gimme.prototype.parent = function(n)
	{
		return this.entities[n || 0].parentNode;
	};
	
	// performs a ComplexGimme using the element from this.entities[0] as the root
	gimme.prototype.select = function(_selector)
	{
		var root = this.entities[0];
		return new gimme(ComplexGimme(_selector, root));
	};
	
	// adds the event listener to each element in the entities array
	gimme.prototype.addEvent = function(_evtName, _fn, _useCapture)
	{
		this.entities.forEach(function(_elem, _index)
		{
			// W3C
			if (typeof _elem.addEventListener !== 'undefined')
			{
				if (_evtName === 'mouseenter')
				{
					_elem.addEventListener('mouseover', MouseEnter(_fn), _useCapture);
				}
				else if (_evtName === 'mouseleave')
				{
					_elem.addEventListener('mouseout', MouseEnter(_fn), _useCapture);
				} 
				else
				{
					_elem.addEventListener(_evtName, _fn, _useCapture);
				}
			}
			// IE
			else if (typeof _elem.attachEvent !== 'undefined')
			{
				// create a key to identify this element/event/function combination
				var key = '{' + IE_GetUniqueId(_elem) + '/' + _evtName + '/' + GetObjectGUID(_fn) + '}';
				
				// if this element/event/combo has already been wired up, just return
				var f = evtHash[key];
				if (typeof f !== 'undefined')
					{ return; }
				
				// create a helper function to fix IE's lack of standards support
				f = function(e)
				{
					// map .target to .srcElement
					e.target = e.srcElement;
					
					// map .relatedTarget to either .toElement or .fromElement
					if (_evtName == 'mouseover') { e.relatedTarget = e.fromElement; }
					else if (_evtName == 'mouseout') { e.relatedTarget = e.toElement; }
						
					e.preventDefault = function() { e.returnValue = false; };
					e.stopPropagation = function() { e.cancelBubble = true; };
					
					// call the actual function, using entity (the element) as the 'this' object
					_fn.call(_elem, e);
					
					// null out these properties to prevent memory leaks
					e.target = null;
					e.relatedTarget = null;
					e.preventDefault = null;
					e.stopPropagation = null;
					e = null;
				};
				
				// add the helper function to the event hash
				evtHash[key] = f;

				// hook up the event (IE style)
				_elem.attachEvent('on' + _evtName, f);
		
				// attach unload event to the window to clean up possible IE memory leaks
				window.attachEvent('onunload', function()
				{
					_elem.detachEvent('on' + _evtName, f);
				});
						
				key = null;
				//f = null;   /* DON'T null this out, or we won't be able to detach it */
			}
		});
		
		return this;
	};

	// removes the event listener from each element in the entities array
	gimme.prototype.removeEvent = function(_evtName, _fn, _useCapture)
	{
		this.entities.forEach(function(_elem, _index)
		{
			// W3C
			if (typeof _elem.removeEventListener !== 'undefined')
			{
				if (_evtName === 'mouseenter')
				{
					_elem.removeEventListener('mouseover', MouseEnter(_fn), _useCapture);
				}
				else if (_evtName === 'mouseleave')
				{
					_elem.removeEventListener('mouseout', MouseEnter(_fn), _useCapture);
				}
				else
				{
					_elem.removeEventListener(_evtName, _fn, _useCapture);
				}
			}
			// IE
			else if (typeof _elem.detachEvent !== 'undefined')
			{
				// grab the event handler function from the hash based on its key
				var key = '{' + IE_GetUniqueId(_elem) + '/' + _evtName + '/' + GetObjectGUID(_fn) + '}';
				var f = evtHash[key];
				if (typeof f !== 'undefined')
				{
					// unhook the event and delete the element from the event hash
					_elem.detachEvent('on' + _evtName, f);
					delete evtHash[key];
				}

				// prevent IE memory leaks
				key = null;
				f = null;
			}
		});
		
		return this;
	};

	// sets the .innerHTML of all elements in the entities array to _html
	gimme.prototype.setHTML = function(_html)
	{
		this.entities.forEach(function(_elem, _index)
		{
			_elem.innerHTML = _html;
		});

		return this;
	};

	// retrieves the .innerHTML of the nth element in the entities array (n defaults to 0)
	gimme.prototype.getHTML = function(n)
	{
		return this.entities[n || 0].innerHTML;
	};

	// sets the .value (or .nodeValue in the case of textNodes) of all elements in the entities array to _val
	gimme.prototype.setValue = function(_val)
	{
		this.entities.forEach(function(_elem, _index)
		{
			var v = _elem.nodeType === 3 ? 'nodeValue' : 'value';
			_elem[v] = _val;
		});
		
		return this;
	};

	// retrieves the .value of the nth element in the entities array (n defaults to 0)
	gimme.prototype.getValue = function(n)
	{
		return this.entities[n || 0].value;
	};

	// retrieves the computed style of the nth element in the entities array (n defaults to 0)
	gimme.prototype.getStyle = function(_style, n, _smart)
	{
		var elem = this.entities[n || 0];
		
		// TODO:  Document this rather confusing piece of code and why it's necessary
		if (_style === 'opacity')
		{
			var opacity = GetOpacity(elem);
			if (isNaN(opacity))
			{
				_style = opacity;
			}
			else
			{
				return opacity;
			}
		}
		
		// _smart, which default to true, means that the function will attempt to calculate
		// the 'bottom' or 'right' styles based on the 'top' or 'left' styles (if it needs to)
		if (_smart !== false) { _smart = true; }
	
		var computedStyle = '';						// will be set to a computedStyle object in W3C browsers, and a currentStyle object in IE
		
		// if the browser supports .defaultView.getComputedStyle, life is easy -- just use that
		if (typeof document.defaultView !== 'undefined' && typeof document.defaultView.getComputedStyle !== 'undefined')
		{
			computedStyle = document.defaultView.getComputedStyle(elem, null);
		}
		// otherwise, crap, we've got work to do
		else if (typeof elem.currentStyle !== 'undefined')
		{	
			// start by retrieving the element's .currentStyle property (this won't be normalized to pixels)
			computedStyle = elem.currentStyle;
			
			// If the style being requested is either of the positions: top or left, we'll use .offsetTop and .offsetLeft
			// * WARNING:
			// * Browsers have sharp disagreements about what .offsetTop and .offsetLeft mean, but it just so "happens"
			// * that in IE .offsetTop and .offsetLeft are equal to the computed top and the computed left. Firefox usually
			// * agrees with IE on this, but Konqueror, Opera, and Safari all disagree (with IE and FF).
			// * The major flaw in this function's logic is that it assumes .offsetTop and .offsetLeft are going to be the same
			// * as the computed top and computed left if the browser supports .currentStyle (and not .defaultView.getComputedStyle).
			// * At present, this appears to be true, and I suspect that it will always be true, but there is no guarantee.				
			
			// attempt to get the requested style
			var val = computedStyle[_style];
			
			// if the value is 'auto', it most likely means that it was never specified in the stylesheet
			if (val === 'auto')
			{
				// if height was requested, try .clientHeight; if that fails, try .offsetHeight
				if (_style === 'height')
				{
					return (elem.clientHeight || elem.offsetHeight) + 'px';
				}
				
				// if width was requested, try .clientWidth; if that fails, try .offsetWidth
				if (_style === 'width')
				{
					return (elem.clientWidth || elem.offsetWidth) + 'px';
				}
				
				// if top was requested, use .offsetTop
				if (_style === 'top')
				{
					return elem.offsetTop + 'px';
				}
				// if left was requested, use .offsetLeft
				else if (_style === 'left')
				{
					return elem.offsetLeft + 'px';
				}
				// If bottom or right were requested, AND if _smart (true by default) was specified, then
				// try to calculate right based on left or bottom based on top
				else if (_smart && (_style === 'right' || _style === 'bottom'))
				{
					// flip serves as an easy was to request the compliment property
					var flip = { bottom: [ 'top', 'Height' ], right: [ 'left', 'Width' ] };
					
					// get the computedStyle of the compliment property (make sure _smart is false so we don't recurse infinitely)
					var compliment = parseInt(this.getComputedStyle(elem, flip[_style][0], false));
					
					// return the calculated bottom or right based on the known top or left
					return (elem.parentNode['client' + flip[_style][1]] - elem['offset' + flip[_style][1]] - compliment) + 'px';
				}
				// if val is 'auto', but none of the above apply, assume 0px (this might not be safe -- need to look into this more)					
				else
				{
					return '0px';
				}
			}
			
			// Some values should not be converted to pixels (like hex color values for instance), so this regex aids in
			// determining when to try converting to pixels (specifically, only for valid units that are not pixels)
			var nonPixels = /(em|ex|%|in|cm|mm|pt|pc|small|medium|large|thin|thick)$/;
			
			// Based on the 
			var borderWidth = /border(.*)Width/i;
			var borderStyle = borderWidth.test(_style) ? _style.replace(borderWidth, 'border$1Style') : null;				
			
			// only bother attempting to convert valid units that are not already pixels
			if (nonPixels.test(val))
			{					
				// IE does something interesting.  If you ask for border[Top|Right|Bottom|Left]Width, IE will give it to you... NO MATTER WHAT!
				// Depending on who you ask, this may or may not make sense.  But it doesn't matter, it won't work for this function.
				// If, for example, the borderTopWidth is 5px, but the borderTopStyle is 'none', then we need to report borderTopWidth as 0!
				if (borderStyle !== null && computedStyle[borderStyle] === 'none')
					{ return '0px'; }
				else
					{ return ConvertToPixels(val, elem); }
			}
		}
		return computedStyle[_style];
	};
	
	// set a specified style of all elements in the entities array to the desired value
	gimme.prototype.setStyle = function(_style, _val)
	{
		this.entities.forEach(function(_elem, _index)
		{
			switch (_style)
			{
				case 'opacity':
					SetOpacity(_elem, _val);
					break;
					
				default:
					_elem.style[_style] = _val;
					break;
			}
		});
		
		return this;
	};
	
	// adds a class (if the element doesn't already have it) to each element in the entities arrary
	gimme.prototype.addClass = function(_class)
	{
		// TODO:  decide if multiple classes is worth the overhead
		var classes = _class.split(/\s+/);
		
		this.entities.forEach(function(_elem, _index)
		{
			classes.forEach(function(_className, _j)
			{
				if (!HasClass(_elem, [ _className ]))
				{
					if (_elem.className === '')
					{
						_elem.className = _className;
					}
					else
					{
						_elem.className += ' ' + _className;
					}
				}
			});
		});
		
		return this;
	};

	// removes a class (if the element has it) from each element in the entities array
	gimme.prototype.removeClass = function(_class)
	{
		return this.swapClass(_class, '$1');
	};

	// swap _oldClass with _newClass for each element in the entities array
	gimme.prototype.swapClass = function(_oldClass, _newClass)
	{
		// if _newClass is '$1' it means we're actually just removing _oldClass (and not replacing it with anything)
		// however, if _newClass is anything other than $1, we need to pad it with spaces 
		if (_newClass !== '$1')	{ _newClass = ' ' + _newClass + ' '; }
		
		// TODO:  decide if multiple classes is worth the overhead
		var classes = _oldClass.split(/\s+/);
		
		this.entities.forEach(function(_elem, _index)
		{
			var tmpClass = _elem.className;
			
			classes.forEach(function(_className, _j)
			{
				var pattern = new RegExp('(^| )' + _className + '( |$)');
				tmpClass = tmpClass.replace(pattern, _newClass);
			});
			
			_elem.className = tmpClass.normalize();
		});
		
		return this;
	};

	// returns true if entities[n] has the specified _class
	gimme.prototype.hasClass = function(_class, n)
	{
		return HasClass(this.entities[n || 0], [ _class ]);
	};
	
	gimme.prototype.getAncestor = function(_distance, n)
	{
		var elem = this.entities[n || 0];
		var i = _distance;
		while (i-- > 0)
		{
			if (elem)
			{
				elem = elem.parentNode;
			}
			else
			{
				break;
			}			
		}
		
		return elem;
	};

	gimme.prototype.getSibling = function(_distance, n)
	{
		var elem = this.entities[n || 0];
		if (_distance === 0)
		{
			return elem;
		}
		
		var sibling = _distance > 0 ? 'nextSibling' : 'previousSibling';

		var sib = elem;
		var count = Math.abs(_distance);

		var i = 0;
		while (i < count)
		{
			sib = sib[sibling];
			if (!sib)
			{
				break;
			}
			if (sib.nodeType !== 3)
			{
				i++;
			}
		}

		return sib;
	};
	
	// attempts to "read" the given attribute value of element n in the entities array (n defaults to 0)
	gimme.prototype.readAttribute = function(_attr, n)
	{
		return ReadAttribute(this.entities[n || 0], _attr);
	};

	// filters the entities array based on the given _filterFn (if _filterFn returns true, the element will remain in the entities array, if false, the element will be removed)
	gimme.prototype.filter = function(_filterFn)
	{
		var entities = this.entities;
		var i = 0, len = entities.length;
		while (i < len)
		{
			if (!_filterFn(entities[i]))
			{
				entities.splice(i, 1);
				len--;
			}
			else
			{
				i++;
			}
		}
		
		return this;
	};

	// executes a callback function on each element in the entities array
	gimme.prototype.iterate = function(_callback)
	{
		var entities = this.entities;
		var i, len = entities.length;
		for (i = 0; i < len; i++)
		{
			var gElem = g(entities[i]);
			_callback.call(gElem, i);
		}
		
		_callback = null;
		
		return this;
	};
	
	// == Private gimme (helper functions used mostly by the little gimme) == /
	function IE_GetUniqueId(_elem)
	{
		return _elem === window ? 'theWindow' : _elem === document ? 'theDocument' : _elem.uniqueID;
	}
	
	function GetObjectGUID(_obj)
	{
		if (typeof _obj.guid === 'undefined')
			{ _obj.guid = Math.random(new Date().getTime()); }
			
		return _obj.guid;
	}
	
	function MouseEnter(_fn)
	{
		var key = GetObjectGUID(_fn);
		var f = evtHash[key];
		if (typeof f === 'undefined')
		{
			f = evtHash[key] = function(_evt)
			{
				var relTarget = _evt.relatedTarget;
				if (this === relTarget || IsAnAncestorOf(this, relTarget)) { return; }
		
				_fn.call(this, _evt);
			};
		}
		return f;
	}
	
	var SetOpacity = function()
	{
		function w3c_opacity(_elem, _amt) { _elem.style.opacity = _amt; }
	
		function moz_opacity(_elem, _amt) { _elem.style.MozOpacity = _amt; }
		
		function khtml_opacity(_elem, _amt) { elem.style.KhtmlOpacity = _amt; }
		
		function ie_opacity(_elem, _amt) { _elem.style.filter = 'alpha(opacity=' + (_amt * 100) + ')'; }
	
		var fn, d = document.createElement('div');
		if (typeof d.style.opacity !== 'undefined')
		{
			fn = w3c_opacity;
		}
		else if (typeof d.style.filter !== 'undefined')
		{
			fn = ie_opacity;
		}
		else if (typeof d.style.MozOpacity !== 'undefined')
		{
			fn = moz_opacity;
		}
		else if (typeof d.style.KhtmlOpacity !== 'undefined')
		{
			fn = khtml_opacity;
		}
		else
		{
			fn = function() {};			// No opacity support I guess :-(
		}
		
		d = null;
		return fn;
	}();
	
	var GetOpacity = function()
	{
		function w3c_opacity(_elem)	{ return parseFloat(_elem.style.opacity) || 'opacity';	}

		function moz_opacity(_elem) {	return parseFloat(_elem.style.MozOpacity) || 'MozOpacity'; }
		
		function khtml_opacity(_elem)	{ return parseFloat(_elem.style.KhtmlOpacity) || 'KhtmlOpacity'; }
		
		function ie_opacity(_elem)
		{
			var filter = _elem.currentStyle['filter'];
			var match = filter.match(/pacity\s*=\s*(\d{1,3}.?\d*)\)/);
			if (!match)
			{
				return 1;
			}
			else
			{
				return parseFloat(match[1]) / 100;
			}
		}
		
		var fn, d = document.createElement('div');
		if (typeof d.style.opacity !== 'undefined')
		{
			fn = w3c_opacity;
		}
		else if (typeof d.style.filter !== 'undefined')
		{
			fn = ie_opacity;
		}
		else if (typeof d.style.MozOpacity !== 'undefined')
		{
			fn = moz_opacity;
		}
		else if (typeof d.style.KhtmlOpacity !== 'undefined')
		{
			fn = khtml_opacity;
		}
		else
		{
			fn = function() {};			// No opacity support I guess :-(
		}
		
		d = null;
		return fn;
	}();
	
	function ConvertToPixels(_str, _context)
	{
		// if the units are already pixels, no conversion is necessary, just parseInt and return
		if (/px$/.test(_str)) { return parseInt(_str); }
		
		// if no _context was specified, default to the <body>
		if (!_context) { _context = document.body; }
		
		// If we get here, we've got some work to do:
		// 1. Create an invisible div that we'll use to measure the style represented by _str
		// 2. Make sure its position is absolute so it doesn't shift other elements on the page
		// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
		var tmp = document.createElement('div');
		tmp.style.visbility = 'hidden';
		tmp.style.position = 'absolute';				
		tmp.style.lineHeight = '0';				
		
		// Two additional checks are needed before we conver to pixels
		// 1.  If we're tyring to convert a % to pixels, we have to do it in the context of a parent element
		// 2.  Or if the _context element is an <img>, we also need to use the parent element, b/c we're going to append the
		// invisible div (tmp) to the _context, but it's not valid to append an element to an <img> tag
		if (/%$/.test(_str) || _context.tagName.toLowerCase() === 'img')
		{
			_context = _context.parentNode || _context;
			tmp.style.height = _str;
		}
		else
		{
			tmp.style.borderStyle = 'solid';
			tmp.style.borderBottomWidth = '0';					
			tmp.style.borderTopWidth = _str;
		}
		
		_context.appendChild(tmp);
		var px = tmp.offsetHeight;
		_context.removeChild(tmp);
		
		return px || 0;	
	};
	
	
	//  ParseSelector Function
	//  args:  _selector (string, required)
	//  returns:  an array of SimpleSelector objects
	//  notes: _selector should not contain any extraneous spaces (e.g. only those spaces indicating a descendant selector are allowed; all others should be stripped beforehand)
	function ParseSelector(_selector)
	{
		var type = 0;												// type of the current part (0 = tag, 1 = id, 2 = class, 3 = attribute, 4 = pseudo)
		var endings = [ '.#[:', '.[:', '.#[:', ']', '.#[:' ];		// array of string whose characters represent endings for tag, id, class or attribute ( endings[0] for tags, endings[1] for ids, etc...)
		var combinators = ' >~+';									// combinators (the 4 characters that can separate simple selectors)
		
		var part = '';												// current part (of a SimpleSelector) that is being built (e.g. span in "span.cat" or zoo in "div#zoo")
		var possibleEndings = endings[type];						// initially, the possible ending characters for a tag
		
		var selectorArray = [];										// the array of SimpleSelector objects to be returned
		var simpleSelector = new SimpleSelector();					// the current SimpleSelector object that is being built 
		
		var c;														// current character in the _selector string
		var i, len = _selector.length;								// looping variable, cached _selector length		
		for (i = 0; i < len; i++)
		{
			c = _selector.charAt(i);
			if (possibleEndings.indexOf(c) !== -1)
			{
				if (part !== '')
				{
					simpleSelector.addPart(part, type);
					part = '';
				}

				switch (c)
				{
					case '#': possibleEndings = endings[type = 1]; continue;
					case '.': possibleEndings = endings[type = 2]; continue;
					case '[': possibleEndings = endings[type = 3]; continue;
					case ']': possibleEndings = endings[type = 0]; continue;
					case ':': possibleEndings = endings[type = 4]; continue;
				}
			}
			else if (type !== 3 && (combinators.indexOf(c) !== -1))
			{
				selectorArray.push(simpleSelector.addPart(part, type));
				selectorArray.push(c);											// in this case, c represents a combinator; we want to capture that!
				part = '';														// reset the part variable to an empty string for the next iteration
				simpleSelector = new SimpleSelector();							// reset the simpleSelector variable for the next iteration
				possibleEndings = endings[type = 0];							// reset the type variable (to tag) for the next iteration, also reset the possibleEndings variable to reflect the new type
			}
			else
			{
				// If the character, c, is not an ending character for the current part type,
				// and it's not a combinator character (when type is not attribute),
				// then just continue building the part
				part += c;
			}
		}

		// Once the entire _selector string has been examined, check the part variable to see if it has data.  If so, add it to the current simpleSelector.
		if (part !== '') { simpleSelector.addPart(part, type); }
		
		// Check the current simpleSelector itself, and if it isn't empty, add it to the selectorArray to be returned
		if (!simpleSelector.isEmpty) { selectorArray.push(simpleSelector); }

		return selectorArray;
	}
	
	function ParseAttribute(_attr)
	{
		var pattern = /=|\^=|\$=|\*=|\|=|!=/;
		var delim = _attr.match(pattern);
		delim = delim && delim[0];
		var avPair = _attr.split(delim);
		var attr = avPair[0] && avPair[0].replace(/[\[\]"]/g, '').trim();
		var val = avPair[1] && avPair[1].replace(/[\[\]"]/g, '').trim();
		return { name: attr, delim: delim, val: val };
	}
	
	
	function ComplexGimme(_selector, _root, _gimmeCallback)
	{
		// no selector?  no objects for you!
		if (!_selector) { return []; }
		
		// if it's already an array, just return it
		if (_selector.constructor === Array) { return _selector; }
		
		// not a string and not an array?  assume it's a DOM element and return it as the single element in an array
		if (typeof _selector !== 'string') { return [ _selector ]; }
		
		// get ready to match some elements!
		var matched = [];
		
		// normalize the _selector string (collapse all occurrences of 2 or more spaces down to 1 space, and trim beginning and ending white space)
		// also, trim white space from around +, >, and ~ combinators
		_selector = _selector.normalize().replace(/\s*(\+|>|~|,)\s*/g, '$1');
		
		// parse the _selector string into an array of SimpleSelector objects
		var simpleSelectors = ParseSelector(_selector);
		
		// looping variables and length indicators
		var i = 0, j = 0, k = 0, numSelectors = simpleSelectors.length;
		
		// all elements matches by the last selector in a complex selector (e.g.  span.cat in "body > div div + p span.cat")
		var elems = SimpleGimme(simpleSelectors[numSelectors - 1], _root);
		
		passed = true;								// indicates whether or not the element has passed all simple selectors 
		
		var tmp,									// all: tmp variable used to execute SimpleGimme during the looping logic
		isPrecedingSibling,							// (~): indicates whether or not the currentElem is a preceding sibling of elem
		elem,										// all: represents the current element during the iteration of elems (all elements returned from the last selector in a complex gimme)
		currentElem,								// all: a copy of elem, used throughout the looping logic because elem itself needs to maintain its value, whereas currentElem may be modified during the loops
		prevSib,									// (+): represents the previous sibling of currentElem (not necessarily of elem)
		siblings,									// (~): all the siblings of elem
		parent,										// (>): represents the parent of currentElem (not necessarily of elem
		ancestor;									// ( ): represents an ancestor of currentElem (could be several generations removed)
		
		var numElems = elems.length;
		for (i = 0; i < numElems; i++)
		{
			k = 0;
			isPrecedingSibling = true;
			ancestor = currentElem = elem = elems[i];
			
			selectorLoop:
			for (j = numSelectors - 3; j >= 0; j -= 2)
			{
				var combinator = simpleSelectors[j + 1];
				var currSelector = simpleSelectors[j];

				switch (combinator)
				{
					// descendant combinator
					case ' ':
						ancestor = ancestor.parentNode;
						while (ancestor && ancestor !== document)
						{
							if (ValidateSingleElement(ancestor, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = ancestor;
								continue selectorLoop;
							}
							ancestor = ancestor.parentNode;
						}
						passed = false;
						break selectorLoop;

					// direct descendant (child) combinator
					case '>':
						while (parent !== document)
						{
							parent = currentElem.parentNode;
							if (ValidateSingleElement(parent, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = parent;
								continue selectorLoop;
							}
							else if (simpleSelectors[j + 3] !== ' ')
							{
								break;
							}
							currentElem = parent;
						}
						passed = false;
						break selectorLoop;

					// adjacent sibling combinator
					case '+':
						prevSib = GetPreviousSibling(currentElem);
						if (prevSib)
						{
							if (ValidateSingleElement(prevSib, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = prevSib;
								continue selectorLoop;
							}
						}
						passed = false;
						break selectorLoop;

					// general sibling combinator
					case '~':
						siblings = currentElem.parentNode.childNodes;
						var len = siblings.length;
						while (k < len)
						{
							var sib = siblings[k++];
							if (sib.nodeType === 3)
							{
								continue;
							}
							
							if (sib === elem)
							{
								isPrecedingSibling = false;
								break;
							}							
							
							if (ValidateSingleElement(sib, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = sib;
								continue selectorLoop;
							}
						}
						passed = false;
						break selectorLoop;
				}
			}

			if (passed)
			{
				matched.push(elem);		
				if (typeof _gimmeCallback === 'function')
				{
					_gimmeCallback.call(new gimme(elem));
				}
			}
			passed = true;
		}
		
		_gimmeCallback = null;
		return matched;
	}
		
	function SimpleGimme(_selector, _root)
	{	
		var root = _root || document;
		var id = _selector.id;
		var tag = _selector.tag;
		var classes = _selector.classes;
		var attributes = _selector.attributes;
		var pseudos = _selector.pseudos;
		
		if (id)
		{
			var elem = document.getElementById(id);
			return ValidateSingleElement(document.getElementById(id), null, tag, classes, attributes, pseudos) ? [ elem ] : [];
		}
		else
		{
			tag = tag || '*';
			var elems = root.getElementsByTagName(tag);
			return FilterElements(elems, tag === '*' ? null : tag, classes, attributes, pseudos);
		}
	}
	
	function HasClass(_elem, _classes)
	{
		var className = _elem.className;
		if (typeof className !== 'string')
		{
			className = _elem.getAttribute('class');
		}
		
		if (!className)
		{
			return false;
		}
		 
		var elemClasses = className.split(' ');
		var i, len = _classes.length;
		for (i = 0; i < len; i++)
		{
			if (!elemClasses.contains(_classes[i]))
			{
				return false;
			}
		}
		
		return true;
	}
	
	function HasAttribute(_elem, _attributes)
	{
		var result = false;
		var i, len = _attributes.length;
		var attr, attrName, attrVal, attrDelim, elemAttr, lio;
		for (i = 0; i < len; i++)
		{
			attr = _attributes[i];
			attrName = attr.name;
			attrVal = attr.val;
			attrDelim = attr.delim;
			elemAttr = ReadAttribute(_elem, attrName);
			if (!elemAttr)
			{
				continue;
			}
			
			switch (attrDelim)
			{
				case '=':
					result = elemAttr === attrVal;					
					break;
					
				case '^=':
					result = elemAttr.indexOf(attrVal) === 0;
					break;
					
				case '$=':
					lio = elemAttr.lastIndexOf(attrVal);
					result = lio !== -1 && lio + attrVal.length === elemAttr.length;
					break;
					
				case '*=':
					result = elemAttr.indexOf(attrVal) !== -1;
					break;
					
				case '|=':
					result = (elemAttr === attrVal) || (elemAttr.indexOf(attrVal + '-') === 0);
					break;
					
				case '!=':
					result = elemAttr !== attrVal;
					break;
					
				case null:
				default:
					result = elemAttr !== null;
					break;					
			}
			
			if (!result)
			{
				break;
			}
		}

		// only return if the element has all of the specified attributes
		return result;
	}
	
	function HasPseudo(_elem, _pseudos)
	{
		var parent, children, numChildren, firstChild, lastChild;
		var result = false;
		var i, j, len = _pseudos.length;
		for (i = 0; i < len; i++)
		{
			parent = _elem.parentNode;
			children = parent.childNodes;
			numChildren = children.length;
			
			switch(_pseudos[i])
			{
				case 'first-child':
					for (j = 0; j < numChildren; j++)
					{
						firstChild = children[j];
						if (firstChild.nodeType === 1)
						{
							break;
						}
					}
					if (firstChild.nodeType !== 1)
					{
						result = false;
					}
					else
					{
						result = firstChild === _elem;
					}
					break;
					
				case 'last-child':
					for (j = numChildren - 1; j >= 0; j--)
					{
						lastChild = children[j];
						if (lastChild.nodeType === 1)
						{
							break;
						}
					}
					if (lastChild.nodeType !== 1)
					{
						result = false;
					}
					else
					{
						result = lastChild === _elem;
					}
					break;
				
				/*
				case 'nth-child:'
					break;
				*/
					
				default:
					result = false;
					break;
			}
			
			if (!result)
			{
				break;
			}
		}
		
		return result;
	}
	
	function IsAnAncestorOf(_ancestor, _descendant, _generation)
	{
		if (_ancestor === _descendant) { return false; }
		
		var gen = 0;
		while (_descendant && _descendant != _ancestor)
		{
			gen++;
			_descendant = _descendant.parentNode;
		}
		
		_generation = _generation || gen;
		return _descendant === _ancestor && _generation === gen;		
	}
	
	function IsASiblingOf(_sib1, _sib2)
	{
		// TODO: implement
	}
	
	function ReadAttribute(_elem, _attr)
	{
		return _elem.getAttribute(_attr, 2) || (_elem.attributes && _elem.attributes[_attr] ? _elem.attributes[_attr].nodeValue : _elem.getAttribute(_attr));
	}

	function GetPreviousSibling(_elem)
	{
		var prevSib = _elem.previousSibling;
		while (prevSib && prevSib.nodeType == 3)
		{
			prevSib = prevSib.previousSibling;
		}

		return prevSib;
	}
	
	function FilterElements(_elems, _tag, _classes, _attributes, _pseudos)
	{
		var elem;
		var matched = [];
		var i, len = _elems.length;
		for (i = 0; i < len; i++)
		{
			elem = _elems[i];
			
			if (_attributes.length > 0 && !HasAttribute(elem, _attributes))		
				{ continue; }
			
			if (_tag && elem.tagName.toLowerCase() !== _tag.toLowerCase())
				{ continue; }
			
			if (_classes.length > 0 && !HasClass(elem, _classes))
				{ continue; }
				
			if (_pseudos.length > 0 && !HasPseudo(elem, _pseudos))
				{ continue; }
			
			matched.push(elem);
		}
		
		return matched;
	}
	
	function ValidateSingleElement(_elem, _id, _tag, _classes, _attributes, _pseudos)
	{
		if (!_elem) { return false; }
		if (_tag && _tag !== _elem.tagName) { return false; }
		if (_id && _elem.id !== _id) { return false; }
		if (_attributes.length > 0 && !HasAttribute(_elem, _attributes)) { return false; }
		if (_classes.length > 0 && !HasClass(_elem, _classes)) { return false; }		
		if (_pseudos.length > 0 && !HasPseudo(_elem, _pseudos)) { return false; }
		return true;
	}
	
	
	// == Private Helper Classes == /
		
	//  SimpleSelector Class
	//  desc:  A lightweight object that represents a simple CSS selector (e.g. a selector string that does not involve a combinator
	function SimpleSelector()
	{
		this.id = null;
		this.tag = null;
		this.classes = [];
		this.attributes = [];
		this.pseudos = [];
		this.isEmpty = true;
	}
	SimpleSelector.prototype.addPart = function(_part, _type)
	{
		this.isEmpty = false;
		switch (_type)
		{
			case 0:
				this.tag = _part.toUpperCase();
				break;
			case 1:
				this.id = _part;
				break;
			case 2:
				this.classes.push(_part);
				break;
			case 3:
				this.attributes.push(ParseAttribute(_part));
				break;
			case 4:
				this.pseudos.push(_part);
				break;
		}
		
		return this;
	}
	// End SimpleSelector Class


	function G(s, c)
	{
		return new gimme(s, c);
	};
	G.extension = gimme.prototype;

	return G;
	
}();

// Map g to Gimme (for convenience)
var g = Gimme;


// == Augmentation == //
if (typeof Array.prototype.contains === 'undefined')
{
	Array.prototype.contains = function(_item)
	{
		var i, len = this.length;
		for (i = 0; i < len; i++)
		{
			if (this[i] === _item)
			{
				return true;
			}
		}
		
		return false;
	}
}

if (typeof Array.prototype.forEach === 'undefined')
{
	Array.prototype.forEach = function(_callback, _thisObject)
	{
		var i, elem, len = this.length;
		for (i = 0; i < len; i++)
		{
			elem = this[i];
			_callback.call(_thisObject, elem, i, this);
		}
	}
};

if (typeof Array.prototype.map === 'undefined')
{
	Array.prototype.map = function(_fn, _thisObject)
	{
		var result = [];
		this.forEach(function(_el, _i)
		{
			result.push(_fn.call(_thisObject, _el));
		});
		
		return result;
	}
};

String.prototype.trim = function()
{
	return this.replace(/^\s+|\s+$/g, '');
};

String.prototype.normalize = function()
{
	return this.trim().replace(/\s{2,}/g, ' ');
};

Math.fact = function(n)
{
   if (n <= 1) return 1;
    
    var result = 1;
    while (n > 1)
    {
        result *= n--;
    }

    return result;
};

Math.distance = function(x1, y1, x2, y2)
{
	var tmp = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2);
	return Math.sqrt(tmp);
};
// == End Augmentation == //

